#include "ptwrap.hpp"
#include <stdio.h>

// [[ex03ab]]
// DEBUG descr A more fair semaphore.
class semaphore
{
public:
	semaphore(unsigned initval) :
		restcond_(leadcond_), value_(initval), leadn_(0)
	{ }

	int wait(unsigned n = 1)
	{
		pw::lockmutex lm(leadcond_);
		while(leadn_) 
			restcond_.wait();

		leadn_ = n;
		while(value_ < n)
			leadcond_.wait();
		value_ -= n;
		leadn_ = 0;
		restcond_.signal();
		return 0;
	}
	int trywait(unsigned n = 1)
	{
		pw::lockmutex lm(leadcond_);
		if(leadn_ || value_ < n)
			return ETIMEDOUT;
		value_ -= n;
		return 0;
	}
	int timedwait(unsigned n, 
		const struct timespec &abstime)
	{
		pw::lockmutex lm(leadcond_);
		while(leadn_) {
			if (restcond_.timedwait(abstime) == ETIMEDOUT)
				return ETIMEDOUT;
		}

		leadn_ = n;
		while(value_ < n) {
			if (leadcond_.timedwait(abstime) == ETIMEDOUT) {
				leadn_ = 0;
				restcond_.signal();
				return ETIMEDOUT;
			}
		}
		value_ -= n;
		leadn_ = 0;
		restcond_.signal();
		return 0;
	}
	int signal(unsigned n = 1)
	{
		pw::lockmutex lm(leadcond_);
		value_ += n;
		if (leadn_ && value_ >= leadn_)
			leadcond_.signal();
		return 0;
	}

protected:
	// the lead thread waits on it
	pw::pmcond leadcond_; 
	// the rest of the threads wait on this one
	pw::pchaincond restcond_;
	// current value of the semaphore, always positive
	unsigned value_; 
	// value the lead thread is waiting for, or 0
	unsigned leadn_; 
};
// END

// DEBUG {

semaphore sem(0);

class singles1 : public pw::pwthread
{
public:
	void *execute()
	{
		for (int i = 0; i < 10; i++) {
			sem.signal();
			if (sem.trywait() == ETIMEDOUT) {
				fprintf(stderr, "trywait failed\n");
				sem.wait();
			}
		}
		sem.signal();
		fprintf(stderr, "s1 finished\n");
		return 0;
	}
};

class singles2 : public pw::pwthread
{
public:
	void *execute()
	{
		for (int i = 0; i < 10; i++) {
			sem.wait();
			sem.signal();
		}
		fprintf(stderr, "s2 finished\n");
		return 0;
	}
};

class tens : public pw::pwthread
{
public:
	void *execute()
	{
		sem.wait(10);
		fprintf(stderr, "tens succeeded\n");
		sem.signal(10);
		return 0;
	}
};

int main()
{
	tens t;
	singles1 s1[10];
	singles2 s2[10];

	t.start();
	for (int i = 0; i < 10; i++) {
		s1[i].start();
		s2[i].start();
	}
	for (int i = 0; i < 10; i++) {
		s1[i].join();
		s2[i].join();
	}
	t.join();

	struct timespec endtime;
	clock_gettime(CLOCK_REALTIME, &endtime); // the current time
	if (sem.timedwait(11, endtime) == ETIMEDOUT) {
		fprintf(stderr, "timed out as expected\n");
		endtime.tv_sec += 1;
		if (sem.timedwait(10, endtime) == 0)
			fprintf(stderr, "no timeout as expected\n");
	}

	return 0;
}
// DEBUG }
/* Sample output:

trywait failed
trywait failed
trywait failed
trywait failed
trywait failed
trywait failed
trywait failed
trywait failed
trywait failed
tens succeeded
trywait failed
s1 finished
s2 finished
s1 finished
s1 finished
s2 finished
s1 finished
s2 finished
s1 finished
s2 finished
s1 finished
s2 finished
s2 finished
s1 finished
s2 finished
s1 finished
s2 finished
s1 finished
s2 finished
s1 finished
s2 finished
timed out as expected
no timeout as expected

*/
